package com.example.imageloaderdemo.imageloader;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.example.imageloaderdemo.R;
import com.jakewharton.disklrucache.DiskLruCache;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * 图片加载
 *
 * @author wdl
 */
public class ImageLoader {
    private static final String TAG = "ImageLoader";
    private static final int MESSAGE_POST_RESULT = 1;
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;
    /**
     * 50M的存储缓存大小
     */
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private static final int DISK_CACHE_INDEX = 0;
    private static final int TAG_KEY_URI = R.id.image;
    private static final int IO_BUFFER_SIZE = 8 * 1024;

    private final Context mContext;
    private boolean mIsDiskLruCacheCreated = false;
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;
    private ImageResizer mImageResizer = new ImageResizer();

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    /**
     * 创建线程池
     */
    private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE,
            KEEP_ALIVE,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<Runnable>(),
            sThreadFactory);

    /**
     * 使用主线程的Looper创建Handler，这里使用线程池+Handler实现并发与UI访问能力
     */
    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            Bitmap bitmap = result.bitmap;
            //为了防止View复用所导致的列表错误这个问题，在给ImageView设置图片前，需要检查url是否发生改变
            String uri = (String) imageView.getTag(TAG_KEY_URI);

            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(bitmap);
            } else {
                Log.d(TAG, "handleMessage: Set image bitmap, but url has changed, ignored");
            }
        }
    };


    /**
     * 创建内存缓存和磁盘缓存
     *
     * @param context 上下文
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        //获取当前进程可用的最大内存
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //1/8作为缓存使用
        int cacheSize = maxMemory / 8;
        //创建LruCache,提供缓存总容量大小，重写sizeOf方法
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //sizeOf方法计算缓存对象的大小，单位与总容量单位一直
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdir();
        }

        //磁盘剩余空间小于所需磁盘缓存大小则磁盘缓存失效
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                //创建磁盘缓存，设置磁盘缓存创建标志位
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * build一个新的ImageLoader实例
     *
     * @param context 上下文
     * @return ImageLoader实例
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public static ImageLoader build(Context context) {
        return new ImageLoader(context);
    }

    /**
     * 将图片添加到内存缓存
     *
     * @param key    key
     * @param bitmap 图片资源
     */
    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        //如果获取到内存缓存中无当前图片的缓存则缓存到内存中
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     * 获取缓存中的Bitmap
     *
     * @param key key
     * @return 对应key的Bitmap
     */
    private Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }


    /**
     * 获取磁盘的剩余空间大小
     *
     * @param diskCacheDir 文件
     * @return 剩余大小
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    private long getUsableSpace(File diskCacheDir) {
//        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD) {
//            return diskCacheDir.getUsableSpace();
//        }
        final StatFs statFs = new StatFs(diskCacheDir.getPath());
        return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
    }

    /**
     * 获取存储的文件夹
     *
     * @param context    上下文
     * @param uniqueName 图片
     * @return 文件夹
     */
    private File getDiskCacheDir(Context context, String uniqueName) {
        boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if (externalStorageAvailable) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }


    /**
     * 从内存、磁盘、网络加载图片然后绑定到imageView
     *
     * @param uri       http url
     * @param imageView bitmap的绑定对象
     */
    public void bindBitmap(final String uri, final ImageView imageView) {
        bindBitmap(uri, imageView, 0, 0);
    }

    public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap1 = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap1 != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap1);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };

        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }


    /**
     * 同步加载
     *
     * @param uri       uri
     * @param reqWidth  要求的宽
     * @param reqHeight 要求的高
     * @return 图片
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        //从内存中获取
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache: " + uri);
            return bitmap;
        }

        try {
            //从磁盘中获取
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDiskCache: " + uri);
                return bitmap;
            }
            //从网络加载
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp: " + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.d(TAG, "encounter error, DiskLruCache is not created");
            bitmap = downloadBitmapFromUrl(uri);
        }
        return bitmap;
    }

    private Bitmap loadBitmapFromMemCache(String uri) {
        final String key = hashKeyFromUrl(uri);
        return getBitmapFromMemoryCache(key);
    }

    /**
     * 网络下载图片
     *
     * @param url       http url
     * @param reqWidth  要求的宽
     * @param reqHeight 要求的高
     * @return Bitmap
     * @throws IOException 异常
     */
    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException {
        //检查当前所在线程
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network form UI thread （不能在主线程访问网络）");
        }

        //如果磁盘存储为null则直接放回null即可
        if (mDiskLruCache == null) {
            return null;
        }

        //获取key通过对url求hash的方法
        String key = hashKeyFromUrl(url);
        //通过Editor添加磁盘缓存，commit和abort方法用来提交和撤销对于文件的操作
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    /**
     * 从磁盘缓存获取图片
     *
     * @param url       图片url用于生成key
     * @param reqWidth  要求的宽
     * @param reqHeight 要求的高
     * @return 返回Bitmap
     * @throws IOException 获取snapshot时可能出现IO错误
     */
    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {
        //检查当前所在线程
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.d(TAG, "loadBitmapFromDiskCache: load bitmap form UI thread,it is not recommended （不能推荐在主线程加载bitmap）");
        }

        //如果磁盘存储为null则直接放回null即可
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFromUrl(url);

        //通过是snapshot获取内存对象的FileInputStream，然后通过FileDescriptor来加载压缩图片
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if (snapshot != null) {
            FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
            if (bitmap != null) {
                //加入内存缓存
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;
        BufferedOutputStream out = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();

            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    /**
     * 网络下载图片
     *
     * @param uri http uri
     * @return 下载的Bitmap
     */
    private Bitmap downloadBitmapFromUrl(String uri) {
        Bitmap bitmap = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;

        final URL url;
        try {
            url = new URL(uri);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            bitmap = BitmapFactory.decodeStream(in);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return bitmap;
    }

    private String hashKeyFromUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey = bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] digest) {
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : digest) {
            String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }
}
